/*     */ package com.zimbra.cs.servlet;
/*     */ 
/*     */ import com.zimbra.common.jetty.JettyMonitor;
/*     */ import com.zimbra.common.util.Log;
/*     */ import com.zimbra.common.util.ZimbraLog;
/*     */ import java.io.IOException;
/*     */ import java.util.concurrent.ConcurrentHashMap;
/*     */ import java.util.concurrent.atomic.AtomicInteger;
/*     */ import javax.servlet.Filter;
/*     */ import javax.servlet.FilterChain;
/*     */ import javax.servlet.FilterConfig;
/*     */ import javax.servlet.ServletException;
/*     */ import javax.servlet.ServletRequest;
/*     */ import javax.servlet.ServletResponse;
/*     */ import javax.servlet.http.HttpServletRequest;
/*     */ import org.apache.commons.lang.StringUtils;
/*     */ import org.apache.commons.lang.text.StrTokenizer;
/*     */ import org.eclipse.jetty.continuation.Continuation;
/*     */ import org.eclipse.jetty.continuation.ContinuationSupport;
/*     */ import org.eclipse.jetty.util.thread.QueuedThreadPool;
/*     */ import org.eclipse.jetty.util.thread.ThreadPool;
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ 
/*     */ public class ContextPathBasedThreadPoolBalancerFilter
/*     */   implements Filter
/*     */ {
/*     */   static final String RULES_INIT_PARAM = "Rules";
/*     */   static final String SUSPEND_INIT_PARAM = "suspendMs";
/*     */   static final long DEFAULT_SUSPEND_MS = 1000L;
/*     */   ConcurrentHashMap<String, AtomicInteger> activeRequestsByContextPath;
/*     */   ConcurrentHashMap<String, Rules> rulesByContextPath;
/*     */   QueuedThreadPool queuedThreadPool;
/*     */   long suspendMs;
/*     */   
/*     */   public ContextPathBasedThreadPoolBalancerFilter()
/*     */   {
/*  50 */     this.activeRequestsByContextPath = new ConcurrentHashMap();
/*  51 */     this.rulesByContextPath = new ConcurrentHashMap();
/*  52 */     this.queuedThreadPool = null;
/*  53 */     this.suspendMs = 1000L;
/*     */   }
/*     */   
/*     */   public void init(FilterConfig filterConfig) throws ServletException {
/*  57 */     this.suspendMs = 1000L;
/*  58 */     String str = StringUtils.trimToNull(filterConfig.getInitParameter("suspendMs"));
/*  59 */     if (str != null) {
/*  60 */       this.suspendMs = Integer.parseInt(str);
/*     */     }
/*     */     
/*  63 */     str = StringUtils.trimToNull(filterConfig.getInitParameter("Rules"));
/*  64 */     parse(str);
/*  65 */     ZimbraLog.misc.info("Initialized with %s", new Object[] { str });
/*     */     
/*  67 */     ThreadPool threadPool = JettyMonitor.getThreadPool();
/*  68 */     if ((threadPool instanceof QueuedThreadPool)) {
/*  69 */       this.queuedThreadPool = ((QueuedThreadPool)threadPool);
/*  70 */       ZimbraLog.misc.info("Thread pool was configured to max=" + this.queuedThreadPool.getMaxThreads());
/*     */     }
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
/*     */     throws IOException, ServletException
/*     */   {
/*  79 */     boolean suspend = shouldSuspend(request);
/*     */     
/*     */ 
/*  82 */     if (suspend) {
/*  83 */       Continuation continuation = ContinuationSupport.getContinuation(request);
/*  84 */       HttpServletRequest hreq = (HttpServletRequest)request;
/*  85 */       ZimbraServlet.addRemoteIpToLoggingContext(hreq);
/*  86 */       ZimbraServlet.addUAToLoggingContext(hreq);
/*  87 */       ZimbraLog.clearContext();
/*  88 */       continuation.setTimeout(this.suspendMs);
/*  89 */       continuation.suspend();
/*  90 */       return;
/*     */     }
/*     */     
/*     */ 
/*  94 */     String contextPath = getContextPath(request);
/*     */     try
/*     */     {
/*  97 */       AtomicInteger i = (AtomicInteger)this.activeRequestsByContextPath.get(contextPath);
/*  98 */       if (i == null) {
/*  99 */         i = new AtomicInteger(1);
/* 100 */         this.activeRequestsByContextPath.put(contextPath, i);
/*     */       } else {
/* 102 */         i.incrementAndGet();
/*     */       }
/*     */       
/*     */ 
/*     */ 
/* 107 */       chain.doFilter(request, response);
/*     */     }
/*     */     finally {
/*     */       AtomicInteger i;
/* 111 */       AtomicInteger i = (AtomicInteger)this.activeRequestsByContextPath.get(contextPath);
/* 112 */       i.decrementAndGet();
/*     */     }
/*     */   }
/*     */   
/*     */ 
/*     */   protected String getContextPath(ServletRequest request)
/*     */   {
/* 119 */     String uri = ((HttpServletRequest)request).getRequestURI();
/* 120 */     return getContextPath(uri);
/*     */   }
/*     */   
/*     */   protected String getContextPath(String uri)
/*     */   {
/* 125 */     for (String contextPath : this.rulesByContextPath.keySet()) {
/* 126 */       if (uri.startsWith(contextPath)) {
/* 127 */         return contextPath;
/*     */       }
/*     */     }
/* 130 */     return "";
/*     */   }
/*     */   
/*     */   protected String getLoggableContextPath(String contextPath) {
/* 134 */     if ((contextPath == null) || (contextPath.isEmpty())) {
/* 135 */       return "<blank>";
/*     */     }
/* 137 */     return contextPath;
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */ 
/*     */   protected boolean shouldSuspend(ServletRequest request)
/*     */   {
/* 145 */     if (this.queuedThreadPool == null) {
/* 146 */       return false;
/*     */     }
/*     */     
/*     */ 
/* 150 */     String contextPath = getContextPath(request);
/*     */     
/*     */ 
/* 153 */     Rules rules = (Rules)this.rulesByContextPath.get(contextPath);
/* 154 */     if (rules != null)
/*     */     {
/*     */ 
/* 157 */       int threads = this.queuedThreadPool.getThreads();
/* 158 */       int idleThreads = this.queuedThreadPool.getIdleThreads();
/* 159 */       int busyThreads = threads - idleThreads;
/* 160 */       int roomInPoolForNewThreads = this.queuedThreadPool.getMaxThreads() - busyThreads;
/* 161 */       AtomicInteger count = (AtomicInteger)this.activeRequestsByContextPath.get(contextPath);
/* 162 */       int activeRequestsCount = count == null ? 0 : count.intValue();
/* 163 */       ZimbraLog.misc.debug("Servlet (contextPath=%s active=%d), Jetty pool (threads=%d, idle=%d, busy=%d, room=%d)", new Object[] { getLoggableContextPath(contextPath), Integer.valueOf(activeRequestsCount), Integer.valueOf(threads), Integer.valueOf(idleThreads), Integer.valueOf(busyThreads), Integer.valueOf(roomInPoolForNewThreads) });
/*     */       
/*     */ 
/* 166 */       if ((rules.max != null) && 
/* 167 */         (activeRequestsCount > rules.max.intValue())) {
/* 168 */         ZimbraLog.misc.info("Suspending for %dms because context path %s is at %d configured max threads", new Object[] { Long.valueOf(this.suspendMs), getLoggableContextPath(contextPath), Integer.valueOf(activeRequestsCount) });
/* 169 */         return true;
/*     */       }
/*     */       
/*     */ 
/* 173 */       if ((rules.maxPercent != null) && 
/* 174 */         (100 * activeRequestsCount / this.queuedThreadPool.getMaxThreads() > rules.maxPercent.intValue())) {
/* 175 */         ZimbraLog.misc.info("Suspending for %dms because context path %s is at %d threads (%d configured max percentage of thread pool size)", new Object[] { Long.valueOf(this.suspendMs), getLoggableContextPath(contextPath), Integer.valueOf(activeRequestsCount), rules.maxPercent });
/* 176 */         return true;
/*     */       }
/*     */     }
/*     */     
/*     */ 
/* 181 */     return false;
/*     */   }
/*     */   
/*     */ 
/*     */   public void destroy() {}
/*     */   
/*     */   protected void parse(String input)
/*     */     throws ServletException
/*     */   {
/* 190 */     this.rulesByContextPath.clear();
/* 191 */     for (String str : new StrTokenizer(input, ",").getTokenArray()) {
/* 192 */       String[] array = str.split(":");
/* 193 */       if (array.length != 2) {
/* 194 */         throw new ServletException("Malformed rules: " + input);
/*     */       }
/* 196 */       String key = StringUtils.trimToNull(array[0]);
/* 197 */       String value = StringUtils.trimToNull(array[1]);
/* 198 */       if ((key == null) || (value == null)) {
/* 199 */         throw new ServletException("Malformed rules: " + input);
/*     */       }
/* 201 */       Rules rules = Rules.parse(value);
/* 202 */       this.rulesByContextPath.put(key, rules);
/*     */     }
/*     */   }
/*     */   
/*     */   static class Rules { public Integer min;
/*     */     public Integer max;
/*     */     public Integer maxPercent;
/*     */     
/* 210 */     public static Rules parse(String input) throws ServletException { Rules rule = new Rules();
/* 211 */       for (String str : new StrTokenizer(input, ";").getTokenArray()) {
/* 212 */         String[] array = str.split("=");
/* 213 */         if (array.length != 2) {
/* 214 */           throw new ServletException("Malformed rule: " + input);
/*     */         }
/* 216 */         String key = StringUtils.trimToNull(array[0]);
/* 217 */         String value = StringUtils.trimToNull(array[1]);
/* 218 */         if ((key == null) || (value == null)) {
/* 219 */           throw new ServletException("Malformed rule: " + input);
/*     */         }
/* 221 */         switch (key) {
/*     */         case "min": 
/* 223 */           rule.min = new Integer(value);
/* 224 */           break;
/*     */         case "max": 
/* 226 */           if (value.endsWith("%")) {
/* 227 */             rule.maxPercent = new Integer(value.substring(0, value.length() - 1));
/*     */           } else {
/* 229 */             rule.max = new Integer(value);
/*     */           }
/* 231 */           break;
/*     */         default: 
/* 233 */           throw new ServletException("Unknown key: " + key + " in rule: " + input);
/*     */         }
/*     */       }
/* 236 */       return rule;
/*     */     }
/*     */   }
/*     */ }


/* Location:              /home/mint/zimbrastore.jar!/com/zimbra/cs/servlet/ContextPathBasedThreadPoolBalancerFilter.class
 * Java compiler version: 7 (51.0)
 * JD-Core Version:       0.7.1
 */